#!/usr/bin/perl 

## (C) Copyright 2011- Hewlett-Packard Development Company, L.P.
##
## $Id: updateexcludelist.pl 18155 2012-05-18 09:12:45Z cheng-xin.cai@hp.com $

## Script to update exclude lists on a Bright Computing cluster.  The script
## can either add or remove directories to/from the exclude lists.  It will
## generally be called by other scripts (e.g. seaquest_installer or
## remoldinstance.sh), but can be run directly.

use strict;
use warnings;

use File::Basename;
use Getopt::Long;
use POSIX;
use Sys::Hostname;

use lib "/opt/hp/nv/lib/perl";
use bright ':all';
use config ':all';
use instance ':all';

# Globals
my $USER       = $ENV{'USER'};
my $HOSTNAME   = hostname();
my $LOGDIR     = "/tmp/excludelogs-$USER";
my $LOGFILE    = "$LOGDIR/excludelist-$$.log";

##############################################################################
##############################################################################

##
## main
##
sub main()
{
    my %opts = ();

    GetOptions(\%opts,
        "locations|l=s@",
        "add|a",
        "remove|r",
        "help|h"=> \&usage
    ) || usage();

    # Need a directory list and the operation mode
    if (! $opts{locations} || (!$opts{add} && !$opts{remove})) {
        die("Error: Both a directory list and operation mode is required.\n" .
           "See '" . basename($0) . " --help' for more information.\n");
    }

    my @locations = split(/,/, join(',', @{$opts{locations}}));

    # Can't specify both modes at the same time
    if ($opts{add} && $opts{remove}) {
        die("Error: Both 'add' mode and 'remove' mode cannot be specified simultaneously.\n" .
         "See '" . basename($0) . " --help' for more information.\n");
    }


    teeMsg("==================================================");
    teeMsg(scalar(localtime()));
    teeMsg(basename($0) . " : $HOSTNAME");
    teeMsg("Update Bright Computing exclude lists");
    teeMsg("==================================================");

    # Make sure the directory names have the trailing '/'
    my @fixed_locations;
    for my $dir (@locations)
    {
        # For rsync, we wan't the trailing slash
        if (substr($dir, -1, 1) ne "/")
        {
            teeMsg("Adding trailing slash to $dir");
            $dir = "$dir/";
        }

        push(@fixed_locations, $dir);
    }

    validateEnv(\@fixed_locations);

    addMod(@fixed_locations) if ($opts{add});
    removeMod(@fixed_locations) if ($opts{remove});

    exit(0);
}

##
## Make sure this is a Bright cluster, check the paths from the command line.
## The script only supports directories that appear to be valid SeaQuest
## instance paths, which will be of the form /opt/hp/$USER/<INSTANCE_NAME>.
##
## Params: Reference to array containing list of directories to add
##
## Returns: None
##
sub validateEnv($)
{
    my $locs_ref = shift;   # Array reference
    chomp(my $status = `cat /var/spool/cmd/state`);

    teeMsg("Source node is: $HOSTNAME");

    die("Error: This is NOT a Bright Computing Cluster\n")
        if (cluster_type() != config::BRIGHT);

    die("Error: This tool must be executed on the active headnode\n")
        if (uc($status) ne "ACTIVE");

    teeMsg("Exclude list update executed by: " . $ENV{'USER'});
    teeMsg("");

    # Loop through command line directories
    for my $dir (@$locs_ref)
    {
        teeMsg("Target path: $dir");

        if (-d $dir) { teeMsg("Directory path $dir exists on this node"); }
        else { teeMsg("*** Info: Directory path $dir does not exist on this node"); }
    }
}

##
## Adding directories to the exclude lists (new install).
##
## Params: Names of node categories on the system
##         Reference to array containing list of directories to add
##
## Returns: None
##
sub addMod
{
    my @locations = @_;
    my @categories = get_categories();
    my @listnames = qw(excludelistupdate excludelistsyncinstall);
    
    for my $category (@categories) {
        for my $listname (@listnames) {
            my ($ret, $errmsg) = cm_AddExcludeList($category, $listname, @locations);
            die("ERROR: " . $errmsg . "\n") if (! $ret); 

        }
    }
}

##
## Removing directories from the exclude lists (i.e. being called by
## remoldinstance.sh).
##
## Params: Names of node categories on the system
##         Reference to array containing list of directories to remove
##
## Returns: None
##
sub removeMod
{
    my @locations = @_;
    my @categories = get_categories();
    my @listnames = qw(excludelistupdate excludelistsyncinstall);
    
    for my $category (@categories) {
        for my $listname (@listnames) {
            my ($ret, $errmsg) = cm_RemoveExcludeList($category, $listname, @locations);
            die("ERROR: " . $errmsg . "\n" ) if (!$ret);
        }
    }
}

##
## Print a message to STDOUT and also to the log file (as specified by the
## $LOGFILE global).  A newline is appended to the message before writing it
## to the file/screen.
##
## Params: Message string
##
## Returns: None
##
sub teeMsg($)
{
    my $msg = shift;

    mkdir($LOGDIR) if (! -e $LOGDIR);

    open(my $fh, ">>", $LOGFILE) or die("Couldn't open log file for writing: $!\n");

    print $fh "$msg\n";
    print "$msg\n";

    close($fh);
}

##
## Display usage information.
##
sub usage()
{
    my $progname = basename($0);


    print <<"EOF";

Usage: $progname [-l DIR(,DIR,...)] [-a] [-r]

 Add or remove directories from the Bright update and sync exclude lists for
 each node category on the system (there is normally a one-to-one ratio of node
 categories to software images).

 Options:

   -a, --add
       Set operation mode to add entries to the exclude lists.

   -h, --help
       Display usage information.

   -l, --location DIR[,DIR,...]
       Specify directory (or multiple directories) that will be added or
       removed to/from the exclude lists.  The list must be comma-separated
       without spaces between directories.

   -r, --remove
       Set operation mode to remove entries from the exclude lists.

EOF

exit(0);
}

##############################################################################
##############################################################################

main();
